package cn.wp.ewallet.wallet.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.wp.common.core.id.Id;
import cn.wp.ewallet.common.extension.properties.EthProperties;
import cn.wp.ewallet.common.model.dto.PubItemDictDto;
import cn.wp.ewallet.common.service.PubItemDictService;
import cn.wp.ewallet.wallet.extension.constant.EwalletWalletConstant;
import cn.wp.ewallet.wallet.extension.util.DesUtil;
import cn.wp.ewallet.wallet.extension.util.EwalleTransactionUtil;
import cn.wp.ewallet.wallet.extension.util.EwalletWalletUtil;
import cn.wp.ewallet.wallet.model.dto.TransAccountLogDto;
import cn.wp.ewallet.wallet.model.dto.WalletAccountInfoDto;
import cn.wp.ewallet.wallet.model.dto.WalletInfoDto;
import cn.wp.ewallet.wallet.model.entity.WalletInfoDo;
import cn.wp.ewallet.wallet.service.AccountInfoService;
import cn.wp.ewallet.wallet.service.AutoCollectService;
import cn.wp.ewallet.wallet.service.TransAccountLogService;
import cn.wp.ewallet.wallet.service.WalletInfoService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.web3j.crypto.Credentials;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.methods.request.Transaction;
import org.web3j.utils.Convert;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;

/**
 * 自动归集服务
 * Created by zhangyongli on 2020/1/13
 */
@Slf4j
@Service
public class AutoCollectServiceImpl implements AutoCollectService {

    @Autowired
    private PubItemDictService pubItemDictService;

    @Autowired
    private WalletInfoService walletInfoService;

    @Autowired
    private TransAccountLogService transAccountLogService;

    @Autowired
    private AccountInfoService accountInfoService;

    @Autowired
    private EthProperties ethProperties;

    @Override
    public void execute() {

        //查询所有钱包账户信息
        List<WalletInfoDo> list = walletInfoService.getAllByWalletType();
        if (null == list) {
            return;
        }
        EwalleTransactionUtil walleUtil = EwalleTransactionUtil.getInstance();
        //连接web3j
        Web3j web3j = walleUtil.getDefaultConect(ethProperties.getNetwork());

        //循环地址进行归集
        for (WalletInfoDo wa: list) {
            executeCollectByAddress(wa.getWalletAddress(), web3j, walleUtil);
        }
    }

    /**
     *  按地址执行自动归集
     * @param address
     * @param web3j
     * @param walleUtil
     */
    @Override
    public void executeCollectByAddress(String address, Web3j web3j, EwalleTransactionUtil walleUtil) {

        //查询地址全部账户信息
        List<WalletAccountInfoDto> list = walletInfoService.getAllWalletAccountByAddress(address);
        if (CollectionUtil.isEmpty(list)) {
            log.error("地址无账户信息，地址={}", address);
            return;
        }

        //链上资产账户
        List<WalletAccountInfoDto> wList = list.stream().filter(item -> EwalletWalletConstant.ACCOUNT_TYPE_WEB.equals(item.getAccoutType())).collect(Collectors.toList());

        if (CollectionUtil.isNotEmpty(wList)) {
            for (WalletAccountInfoDto dto : wList) {
                autoCollect(dto, web3j, walleUtil);
            }
        }else {
            log.error("账户信息错误，地址={}", address);
        }
    }

    /**
     * 自动归集
     * @param wa
     * @param web3j
     * @param walleUtil
     */
    @Transactional
    protected void autoCollect(WalletAccountInfoDto wa, Web3j web3j, EwalleTransactionUtil walleUtil) {
        //币种名称
        String moneyCode = "USDT";
        //归集币种阈值
        String maxValueItem = EwalletWalletConstant.AUTO_COLLECT_DICT_CODE_MAX_VALUE;
        if (EwalletWalletConstant.MONEY_TYPE_SHQC.equals(wa.getMoneyTypeId())) {
            maxValueItem = EwalletWalletConstant.AUTO_COLLECT_DICT_CODE_MAX_VALUE_SHQC;
            moneyCode = "SHQC";
        }

        log.info("地址={}，币种={}，开始自动归集", wa.getWalletAddress(), moneyCode);
        try {

            //查询最低归集阈值
            PubItemDictDto maxValueDto = pubItemDictService.getPubItemDictByCode(EwalletWalletConstant.AUTO_COLLECT_DICT, maxValueItem);
            Integer minValue = StringUtils.isEmpty(maxValueDto.getItemValue()) ? null : Integer.valueOf(maxValueDto.getItemValue());
            //转成地位最新归集
            BigDecimal decimal = EwalletWalletUtil.toLowerWei(BigDecimal.valueOf(minValue), BigDecimal.valueOf(Double.valueOf(wa.getMoneyTypeUnit())));

            //查询余额
            BigInteger tokenBlance = walleUtil.getTokenBalance(web3j, wa.getWalletAddress(), wa.getContractAddress());
            log.info("代币余额={}, 币种ID={}", tokenBlance,  moneyCode);
            if (tokenBlance.compareTo(decimal.toBigInteger()) == -1) {
                log.info("没有达到归集条件, 地址={}, 币种ID={}", wa.getWalletAddress(), moneyCode);
                return;
            }

            //查询以太坊余额
            BigInteger blance = walleUtil.getBlanceOf(web3j, wa.getWalletAddress());
            //创建交易
            Transaction transaction = walleUtil.getGasLimitTransaction(web3j, wa.getWalletAddress(), ethProperties.getCentralAddress(), tokenBlance, wa.getContractAddress());
            //估算费用
            BigInteger gasLimit = walleUtil.getTransactionGasLimit(web3j, transaction);
            BigInteger gasLimitWei = Convert.toWei(gasLimit.toString(), Convert.Unit.GWEI).toBigInteger();
            if (blance.compareTo(gasLimitWei) == -1) {
                log.info("以太币余额不足,地址={}", wa.getWalletAddress());
                //从中央账户转以太币
                //生成证书
                Credentials credentials = Credentials.create(ethProperties.getPrivateKey());
                String txHash = walleUtil.transto(web3j, wa.getWalletAddress(), credentials, Convert.fromWei(new BigDecimal(gasLimitWei.subtract(blance)), Convert.Unit.GWEI));
                //记录转账交易
                saveTransferInfo(txHash, ethProperties.getCentralAddress(), wa.getWalletAddress(), gasLimitWei.subtract(blance), EwalletWalletConstant.MONEY_TYPE_ETH, "ETH手续费转账", EwalletWalletConstant.TRANS_LOG_TYPE_AUTO_COLLECT_GAS);
                log.info("以太币后续费转账完成，地址={}，手续费={}", wa.getWalletAddress(), gasLimitWei.subtract(blance));
                return;
            }

            //同步资产信息
            if (tokenBlance.compareTo(wa.getMoneyAmount().toBigInteger()) > 0) {
                BigInteger newBlance = tokenBlance.subtract(wa.getMoneyAmount().toBigInteger());
                //同步可用资产账户变动信息
                accountInfoService.saveAccountChange(wa.getWalletAddress(), new BigDecimal(newBlance), EwalletWalletConstant.ACCOUNT_ADD, EwalletWalletConstant.ACCOUNT_CHARGE_CASE_CHANGE_INTO, wa.getMoneyTypeId(), EwalletWalletConstant.ACCOUNT_TYPE_AVAILABLE);
                //同步资产账户变动信息
                accountInfoService.saveAccountChange(wa.getWalletAddress(), new BigDecimal(newBlance), EwalletWalletConstant.ACCOUNT_ADD, EwalletWalletConstant.ACCOUNT_CHARGE_CASE_CHANGE_INTO, wa.getMoneyTypeId(), EwalletWalletConstant.ACCOUNT_TYPE_WEB);
            }

            log.info("链上资产同步完成，地址={}，token数量={}", wa.getWalletAddress(), tokenBlance);

            //生成证书
            Credentials credentials = Credentials.create(DesUtil.init().decryptStr(wa.getSecretKey()));
            //执行转账
            String txHash= walleUtil.transferERC20Token(web3j, wa.getWalletAddress(), ethProperties.getCentralAddress(), tokenBlance, credentials, wa.getContractAddress());

            if (StringUtils.isEmpty(txHash)) {
                log.error("归集失败，地址={}, 币种={}", wa.getWalletAddress(), moneyCode);
                return;
            }
            //记录转账交易
            saveTransferInfo(txHash, wa.getWalletAddress(), ethProperties.getCentralAddress(), tokenBlance, wa.getMoneyTypeId(), moneyCode.concat("自动归集"), EwalletWalletConstant.TRANS_LOG_TYPE_AUTO_COLLECT);
            //同步资产账户变动信息，归集完成后，修改为0
            accountInfoService.saveAccountChange(wa.getWalletAddress(), new BigDecimal(tokenBlance), EwalletWalletConstant.ACCOUNT_SUBTRACT, EwalletWalletConstant.ACCOUNT_CHARGE_CASE_AUTO_COLLECT, wa.getMoneyTypeId(), EwalletWalletConstant.ACCOUNT_TYPE_WEB);

            log.info("归集完成, 地址={}, 币种={}", wa.getWalletAddress(), moneyCode);
        } catch (IOException e) {
            log.error("转账失败，地址={}， msg={}", wa.getWalletAddress(), e.getMessage());
            e.printStackTrace();
        } catch (InterruptedException e) {
            log.error("转账失败，地址={}， msg={}", wa.getWalletAddress(), e.getMessage());
            e.printStackTrace();
        } catch (ExecutionException e) {
            log.error("转账失败，地址={}， msg={}", wa.getWalletAddress(), e.getMessage());
            e.printStackTrace();
        } catch (Exception e) {
            log.error("转账失败，地址={}， msg={}", wa.getWalletAddress(), e.getMessage());
            e.printStackTrace();
        }
    }


    /**
     * 记录转账记录
     * @param txHash
     * @param fromAddress
     * @param toAddress
     * @param tokenBlance
     */
    private void saveTransferInfo(String txHash, String fromAddress, String toAddress, BigInteger tokenBlance, String moenyTypeId, String remark, String operType) {
        //记录转账记录,自动归集时，手续费无法准确计算，总共费用是两种币，不能相加, 记录手续为0，后续更新
        TransAccountLogDto transAccountLogDto = new TransAccountLogDto(Id.gen(), fromAddress, toAddress, new Date(),
                BigDecimal.valueOf(tokenBlance.doubleValue()), BigDecimal.valueOf(0), BigDecimal.valueOf(tokenBlance.doubleValue()),
                EwalletWalletConstant.TRANSFER_OPER_RESULT_WAITING, "system", remark, moenyTypeId, txHash);
        transAccountLogDto.setOperType(operType);
        transAccountLogService.saveTransAccountLog(transAccountLogDto);
    }

    @Override
    public String getCron() {
        //设置默认归集时间
        String cronStr = "0 %s * * * ?";
        PubItemDictDto pubItemDictDo = pubItemDictService.getPubItemDictByCode(EwalletWalletConstant.AUTO_COLLECT_DICT, EwalletWalletConstant.AUTO_COLLECT_DICT_CODE_TIME_FREQ);
        Integer times = StringUtils.isEmpty(pubItemDictDo.getItemValue()) ? null : Integer.valueOf(pubItemDictDo.getItemValue());
        if (null == times) {
            times = 30;
        }
        return String.format(cronStr, "*/"+times);

    }


}
